Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit df7692d7c60f517530c4bb3031ab79ecbb784590


Parents : 7043f33
Author : Mark Qvist <mark@unsigned.io>
Date : 2021-10-03T18:44:00+02:00

Implemented basic LXMF propagation support and controls

Changes

8 files changed, 122 insertions(+), 19 deletions(-)

M setup.py +1 -1

Diff

diff --git a/nomadnet/Conversation.py b/nomadnet/Conversation.py
index a6d3b59..7cb2a8f 100644
--- a/nomadnet/Conversation.py
+++ b/nomadnet/Conversation.py
@@ -196,9 +196,18 @@ class Conversation:
if self.send_destination:
dest = self.send_destination
source = self.app.lxmf_destination
- lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=LXMF.LXMessage.DIRECT)
+ desired_method = LXMF.LXMessage.DIRECT
+ if self.app.directory.preferred_delivery(dest.hash) == DirectoryEntry.PROPAGATED:
+ if self.app.message_router.get_outbound_propagation_node() != None:
+ desired_method = LXMF.LXMessage.PROPAGATED
+
+ lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method)
lxm.register_delivery_callback(self.message_notification)
lxm.register_failed_callback(self.message_notification)
+
+ if self.app.message_router.get_outbound_propagation_node() != None:
+ lxm.try_propagation_on_fail = self.app.try_propagation_on_fail
+
self.app.message_router.handle_outbound(lxm)
message_path = Conversation.ingest(lxm, self.app, originator=True)
@@ -210,7 +219,16 @@ class Conversation:
return False
def message_notification(self, message):
- message_path = Conversation.ingest(message, self.app, originator=True)
+ if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail:
+ RNS.log("Direct delivery of "+str(message)+" failed. Retrying as propagated message.", RNS.LOG_VERBOSE)
+ message.try_propagation_on_fail = None
+ message.delivery_attempts = 0
+ del message.next_delivery_attempt
+ message.packed = None
+ message.desired_method = LXMF.LXMessage.PROPAGATED
+ self.app.message_router.handle_outbound(message)
+ else:
+ message_path = Conversation.ingest(message, self.app, originator=True)
def __str__(self):
string = self.source_hash

diff --git a/nomadnet/Directory.py b/nomadnet/Directory.py
index 6468f04..f64b4f7 100644
--- a/nomadnet/Directory.py
+++ b/nomadnet/Directory.py
@@ -17,6 +17,7 @@ class Directory:
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
app.directory.node_announce_received(destination_hash, app_data, associated_peer)
+ app.autoselect_propagation_node()
def __init__(self, app):
@@ -31,7 +32,7 @@ class Directory:
packed_list = []
for source_hash in self.directory_entries:
e = self.directory_entries[source_hash]
- packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node))
+ packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node, e.preferred_delivery))
directory = {
"entry_list": packed_list,
@@ -59,7 +60,12 @@ class Directory:
else:
hosts_node = False
- entries[e[0]] = DirectoryEntry(e[0], e[1], e[2], hosts_node)
+ if len(e) > 4:
+ preferred_delivery = e[4]
+ else:
+ preferred_delivery = None
+
+ entries[e[0]] = DirectoryEntry(e[0], e[1], e[2], hosts_node, preferred_delivery=preferred_delivery)
self.directory_entries = entries
@@ -130,6 +136,12 @@ class Directory:
else:
return DirectoryEntry.UNKNOWN
+ def preferred_delivery(self, source_hash):
+ if source_hash in self.directory_entries:
+ return self.directory_entries[source_hash].preferred_delivery
+ else:
+ return DirectoryEntry.DIRECT
+
def remember(self, entry):
self.directory_entries[entry.source_hash] = entry
@@ -189,13 +201,21 @@ class DirectoryEntry:
UNKNOWN = 0x02
TRUSTED = 0xFF
- def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False):
+ DIRECT = 0x01
+ PROPAGATED = 0x02
+
+ def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False, preferred_delivery=None):
if len(source_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8:
self.source_hash = source_hash
self.display_name = display_name
if display_name == None:
display_name = source_hash
+ if preferred_delivery == None:
+ self.preferred_delivery = DirectoryEntry.DIRECT
+ else:
+ self.preferred_delivery = preferred_delivery
+
self.trust_level = trust_level
self.hosts_node = hosts_node
else:

diff --git a/nomadnet/NomadNetworkApp.py b/nomadnet/NomadNetworkApp.py
index 862b50c..c55719f 100644
--- a/nomadnet/NomadNetworkApp.py
+++ b/nomadnet/NomadNetworkApp.py
@@ -6,6 +6,8 @@ import RNS
import LXMF
import nomadnet
+from nomadnet.Directory import DirectoryEntry
+
import RNS.vendor.umsgpack as msgpack
from ._version import __version__
@@ -59,7 +61,8 @@ class NomadNetworkApp:
self.firstrun = False
- self.peer_announce_at_start = True
+ self.peer_announce_at_start = True
+ self.try_propagation_on_fail = True
if not os.path.isdir(self.storagepath):
os.makedirs(self.storagepath)
@@ -154,9 +157,9 @@ class NomadNetworkApp:
nomadnet.panic()
- atexit.register(self.exit_handler)
+ self.directory = nomadnet.Directory(self)
- self.message_router = LXMF.LXMRouter()
+ self.message_router = LXMF.LXMRouter(identity = self.identity, autopeer = True)
self.message_router.register_delivery_callback(self.lxmf_delivery)
self.lxmf_destination = self.message_router.register_delivery_identity(self.identity, display_name=self.peer_settings["display_name"])
@@ -171,9 +174,9 @@ class NomadNetworkApp:
RNS.log("LXMF Router ready to receive on: "+RNS.prettyhexrep(self.lxmf_destination.hash))
- self.directory = nomadnet.Directory(self)
-
if self.enable_node:
+ self.message_router.enable_propagation(self.storagepath)
+ RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
self.node = nomadnet.Node(self)
else:
self.node = None
@@ -181,9 +184,13 @@ class NomadNetworkApp:
RNS.Transport.register_announce_handler(nomadnet.Conversation)
RNS.Transport.register_announce_handler(nomadnet.Directory)
+ self.autoselect_propagation_node()
+
if self.peer_announce_at_start:
self.announce_now()
+ atexit.register(self.exit_handler)
+
nomadnet.ui.spawn(self.uimode)
def set_display_name(self, display_name):
@@ -202,6 +209,30 @@ class NomadNetworkApp:
self.peer_settings["last_announce"] = time.time()
self.save_peer_settings()
+ def autoselect_propagation_node(self):
+ nodes = self.directory.known_nodes()
+ trusted_nodes = []
+
+ selected_node = None
+ best_hops = RNS.Transport.PATHFINDER_M+1
+
+ for node in nodes:
+ if node.trust_level == DirectoryEntry.TRUSTED:
+ hops = RNS.Transport.hops_to(node.source_hash)
+
+ if hops < best_hops:
+ best_hops = hops
+ selected_node = node
+
+ if selected_node == None:
+ RNS.log("Could not autoselect a prepagation node! LXMF propagation will not be available until a trusted node announces on the network.", RNS.LOG_WARNING)
+ else:
+ node_identity = RNS.Identity.recall(selected_node.source_hash)
+ propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
+ RNS.log("Selecting "+selected_node.display_name+" "+RNS.prettyhexrep(propagation_hash)+" as default LXMF propagation node", RNS.LOG_INFO)
+ self.message_router.set_outbound_propagation_node(propagation_hash)
+
+
def save_peer_settings(self):
file = open(self.peersettingspath, "wb")
file.write(msgpack.packb(self.peer_settings))
@@ -286,6 +317,10 @@ class NomadNetworkApp:
value = self.config["client"].as_bool(option)
self.peer_announce_at_start = value
+ if option == "try_propagation_on_send_fail":
+ value = self.config["client"].as_bool(option)
+ self.try_propagation_on_fail = value
+
if option == "user_interface":
value = value.lower()
if value == "none":
@@ -430,6 +465,12 @@ downloads_path = ~/Downloads
# to let other peers reach it immediately.
announce_at_start = yes
+# By default, the client will try to deliver a
+# message via the LXMF propagation network, if
+# a direct delivery to the recipient is not
+# possible.
+try_propagation_on_send_fail = yes
+
[textui]
# Amount of time to show intro screen

diff --git a/nomadnet/_version.py b/nomadnet/_version.py
index d1f2e39..48fef32 100644
--- a/nomadnet/_version.py
+++ b/nomadnet/_version.py
@@ -1 +1 @@
-__version__ = "0.1.1"
\ No newline at end of file
+__version__ = "0.1.2"
\ No newline at end of file

diff --git a/nomadnet/ui/TextUI.py b/nomadnet/ui/TextUI.py
index 9b499af..3ace2c0 100644
--- a/nomadnet/ui/TextUI.py
+++ b/nomadnet/ui/TextUI.py
@@ -117,9 +117,10 @@ GLYPHS = {
("node", "[N]", "\u24c3 ", "\uf502"),
("page", "", "\u25a4", "\uf719 "),
("speed", "", "\u25F7", "\uf9c4"),
- ("decoration_menu", " +", " +", " \uf93a"),
- ("unread_menu", " !", " \u2709", urm_char),
+ ("decoration_menu", " +", " +", " \uf93a"),
+ ("unread_menu", " !", " \u2709", urm_char),
("globe", "", "", "\uf484"),
+ ("sent", "/\\", "\u2191", "\ufbf4")
}
class TextUI:

diff --git a/nomadnet/ui/textui/Conversations.py b/nomadnet/ui/textui/Conversations.py
index 6d5ac1c..6cb0123 100644
--- a/nomadnet/ui/textui/Conversations.py
+++ b/nomadnet/ui/textui/Conversations.py
@@ -140,9 +140,12 @@ class ConversationsDisplay():
selected_id_widget = t_id
- untrusted_selected = False
- unknown_selected = True
- trusted_selected = False
+ untrusted_selected = False
+ unknown_selected = True
+ trusted_selected = False
+
+ direct_selected = True
+ propagated_selected = False
try:
if self.app.directory.find(bytes.fromhex(source_hash_text)):
@@ -159,6 +162,11 @@ class ConversationsDisplay():
untrusted_selected = False
unknown_selected = False
trusted_selected = True
+
+ if self.app.directory.preferred_delivery(bytes.fromhex(source_hash_text)) == DirectoryEntry.PROPAGATED:
+ direct_selected = False
+ propagated_selected = True
+
except Exception as e:
pass
@@ -167,6 +175,10 @@ class ConversationsDisplay():
r_unknown = urwid.RadioButton(trust_button_group, "Unknown", state=unknown_selected)
r_trusted = urwid.RadioButton(trust_button_group, "Trusted", state=trusted_selected)
+ method_button_group = []
+ r_direct = urwid.RadioButton(method_button_group, "Deliver directly", state=direct_selected)
+ r_propagated = urwid.RadioButton(method_button_group, "Use propagation nodes", state=propagated_selected)
+
def dismiss_dialog(sender):
self.update_conversation_list()
self.dialog_open = False
@@ -181,7 +193,11 @@ class ConversationsDisplay():
elif r_trusted.state == True:
trust_level = DirectoryEntry.TRUSTED
- entry = DirectoryEntry(source_hash, display_name, trust_level)
+ delivery = DirectoryEntry.DIRECT
+ if r_propagated.state == True:
+ delivery = DirectoryEntry.PROPAGATED
+
+ entry = DirectoryEntry(source_hash, display_name, trust_level, preferred_delivery=delivery)
self.app.directory.remember(entry)
self.update_conversation_list()
self.dialog_open = False
@@ -216,6 +232,9 @@ class ConversationsDisplay():
r_untrusted,
r_unknown,
r_trusted,
+ urwid.Divider(g["divider1"]),
+ r_direct,
+ r_propagated,
known_section,
urwid.Columns([("weight", 0.45, urwid.Button("Save", on_press=confirmed)), ("weight", 0.1, urwid.Text("")), ("weight", 0.45, urwid.Button("Back", on_press=dismiss_dialog))])
])
@@ -718,6 +737,9 @@ class LXMessageWidget(urwid.WidgetWrap):
elif message.lxm.state == LXMF.LXMessage.FAILED:
header_style = "msg_header_failed"
title_string = g["cross"]+" "+title_string
+ elif message.lxm.state == LXMF.LXMessage.SENT:
+ header_style = "msg_header_sent"
+ title_string = g["sent"]+" "+title_string
else:
header_style = "msg_header_sent"
title_string = g["arrow_r"]+" "+title_string

diff --git a/nomadnet/vendor/quotes.py b/nomadnet/vendor/quotes.py
index 90dbecd..ee142f7 100644
--- a/nomadnet/vendor/quotes.py
+++ b/nomadnet/vendor/quotes.py
@@ -1,5 +1,6 @@
quotes = [
("I want the wisdom that wise men revere. I want more.", "Faithless"),
("That's enough entropy for you my friend", "Unknown"),
- ("Any time two people connect online, it's financed by a third person who believes they can manipulate the first two", "Jaron Lanier")
+ ("Any time two people connect online, it's financed by a third person who believes they can manipulate the first two", "Jaron Lanier"),
+ ("The landscape of the future is set, but how one will march across it is not determined", "Terence McKenna")
]
\ No newline at end of file

diff --git a/setup.py b/setup.py
index 0d89182..5c967b5 100644
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,6 @@ setuptools.setup(
entry_points= {
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
},
- install_requires=['rns>=0.2.6', 'lxmf>=0.0.9', 'urwid>=2.1.2'],
+ install_requires=['rns>=0.2.7', 'lxmf>=0.1.0', 'urwid>=2.1.2'],
python_requires='>=3.6',
)
\ No newline at end of file


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────